package vivace.model;

import java.awt.Color;
import java.util.HashSet;
import java.util.Observable;
import java.util.Vector;

/**
 * Models the current UI settings for a project. All settings that shouldn't be
 * "undoable" should be stored in this class. The constructor for this class is protected, 
 * which means that view classes must go through the App-class to create a new UI model.
 * In order to use the methods in this class, go through the App.UI property.
 */
public class UI extends Observable {
	
	private Perspective perspective;
	private EventProperty eventProperty;
	private Tool tool;
	private int zoomLevel;
	private int resolution;
	private HashSet<Integer> trackSelection;
	private HashSet<NoteEvent> noteSelection;
	private boolean snap;
	private boolean discoMode;
	private Vector<Color> trackColors;

	/**
	 * The default track colors
	 */
	private static final Color[] DEFAULT_TRACK_COLORS = {Color.decode("#e76b4a"), Color.decode("#ef9452"), Color.decode("#f7b55a"), 
		Color.decode("#fff76b"), Color.decode("#add66b"),Color.decode("#84c66b"), Color.decode("#4aad6b"),
		Color.decode("#4ab5ad"), Color.decode("#5a84bd"), Color.decode("#6363a5"), Color.decode("#634a8c"),
		Color.decode("#8c528c"), Color.decode("#ad5a94"), Color.decode("#e76b94"), Color.decode("#e76b73"),
		Color.decode("#e76b73"), Color.decode("#97a7b7"), Color.decode("#97a7b7"), Color.decode("#97a7b7")};

	
	/**
	 * Returns the current perspecive
	 * @return
	 */
	public Perspective getPerspective() {
		return perspective;
	}

	/**
	 * Sets the perspective
	 * @param perspective
	 */
	public void setPerspective(Perspective perspective) {
		this.perspective = perspective;
		setChanged(); this.notifyObservers( Action.PERSPECTIVE_CHANGED );
	}

	/**
	 * Returns the selected event property to view (tempo change or velocity)
	 * @return
	 */
	public EventProperty getEventProperty() {
		return eventProperty;
	}

	/**
	 * Sets the selected event property
	 * @param eventProperty
	 */
	public void setEventProperty(EventProperty eventProperty) {
		this.eventProperty = eventProperty;
		setChanged(); this.notifyObservers( Action.EVENT_PROPERTY_CHANGED );
	}
	
	/**
	 * Returns the current tool
	 * @return
	 */
	public Tool getTool() {
		return tool;
	}

	/**
	 * Sets the current tool
	 * @param tool
	 */
	public void setTool(Tool tool) {
		this.tool = tool;
		setChanged(); this.notifyObservers( Action.TOOL_CHANGED );
	}

	/**
	 * Returns the current zoom level
	 * @return
	 */
	public int getZoomLevel() {
		return zoomLevel;
	}

	/**
	 * Sets the current zoom level
	 * @param zoomLevel
	 */
	public void setZoomLevel(int zoomLevel) {
		this.zoomLevel = zoomLevel;
		setChanged(); this.notifyObservers( Action.ZOOMLEVEL_CHANGED );
	}
	
	/**
	 * Returns the current resolution for the grid
	 * @return
	 */
	public int getResolution() {
		return resolution;
	}
	
	/**
	 * Sets the current resolution of the grid
	 * @param resolution
	 */
	public void setResolution(int resolution) {
		this.resolution = resolution;
		setChanged(); this.notifyObservers( Action.RESOLUTION_CHANGED );
	}
	
	/**
	 * Returns the currently selected tracks
	 * @return
	 */
	public HashSet<Integer> getTrackSelection() {
		return trackSelection;
	}

	/**
	 * Sets the currently selected tracks
	 * @param tracks
	 */
	public void setTrackSelection(HashSet<Integer> tracks) {
		trackSelection = tracks;
		setChanged(); this.notifyObservers( Action.TRACK_SELECTION_CHANGED );
	}
	
	/**
	 * Adds the specified track to the selected tracks collection
	 * @param track The track index
	 * @param preserveSelection Whether or not to preserve the currently selected tracks in the collection
	 */
	public void addToTrackSelection( Integer track, boolean preserveSelection ){
		if( !preserveSelection ) {
			trackSelection.clear();
		}
		getTrackSelection().add(track);
		setChanged(); this.notifyObservers( Action.TRACK_SELECTION_CHANGED );
	}

	/**
	 * Clears the selected tracks collection
	 */
	public void clearTrackSelection(){
		trackSelection.clear();
		setChanged(); this.notifyObservers( Action.TRACK_SELECTION_CHANGED );
	}
	
	/**
	 * Returns the currently selected notes
	 * @return
	 */
	public HashSet<NoteEvent> getNoteSelection() {
		return noteSelection;
	}

	/**
	 * Sets the currently selected notes
	 * @param notes
	 */
	public void setNoteSelection(HashSet<NoteEvent> notes) {
		noteSelection = notes;
		setChanged(); this.notifyObservers( Action.NOTE_SELECTION_CHANGED );
	}

	/**
	 * Adds a note to the note selection
	 * @param note The note
	 * @param preserveSelection Whether or not to preserve the currently selected notes
	 */
	public void addToNoteSelection( NoteEvent note, boolean preserveSelection){
		if( !preserveSelection ) {
			noteSelection.clear();
		}
		noteSelection.add(note);
		setChanged(); this.notifyObservers( Action.NOTE_SELECTION_CHANGED );
	}
	
	/**
	 * Clears the selected notes collection
	 */
	public void clearNoteSelection(){
		noteSelection.clear();
		setChanged(); this.notifyObservers( Action.NOTE_SELECTION_CHANGED );
	}

	/**
	 * Returns whether or not snap to grid is enabled
	 * @return
	 */
	public boolean getSnap() {
		return snap;
	}

	/**
	 * Sets whether or not snap to grid is enabled
	 * @param snap
	 */
	public void setSnap(boolean snap) {
		this.snap = snap;
		setChanged(); this.notifyObservers( Action.SNAP_CHANGED );
	}
	
	/**
	 * Returns whether or not disco mode is set
	 * @return
	 */
	public boolean getDiscoMode() {
		return discoMode;
	}

	/**
	 * Sets whether or not disco mode is used
	 * @param discoMode
	 */
	public void setDiscoMode(boolean discoMode) {
		this.discoMode = discoMode;
		setChanged(); this.notifyObservers( Action.DISCOMODE_CHANGED );
	}

	/**
	 * Returns the color for a specified track
	 * @param trackIndex
	 * @return
	 */
	public Color getTrackColor(int trackIndex) {
		return trackColors.get( trackIndex % trackColors.size() );
	}

	/**
	 * Sets the track color for a track
	 * @param trackIndex
	 * @param color
	 */
	public void setTrackColor(int trackIndex, Color color ) {
		if( trackIndex > 0 && trackIndex < trackColors.size() )
			trackColors.set(trackIndex,color);
		else
			trackColors.add(color);
		setChanged(); this.notifyObservers( Action.TRACK_PARAMETERS_EDITED );
	}
	
	/**
	 * Protected constructor, which means the views cannot create new
	 * instances of GUI. They have to go through the App class.
	 */
	protected UI(){
		// Initialize the default settings
		perspective = Perspective.OVERVIEW;
		eventProperty = EventProperty.VELOCITY;
		tool = Tool.POINTER;
		zoomLevel = 100;
		resolution = 1;
		trackSelection = new HashSet<Integer>();
		noteSelection = new HashSet<NoteEvent>();
		snap = false;
		trackColors = new Vector<Color>();
		for( int i = 0; i < DEFAULT_TRACK_COLORS.length; i++ ) // Fill the color array with some nice default colors
			trackColors.add(DEFAULT_TRACK_COLORS[i]);
	}

	// Private method for encapsulating some common code when doing
	// an action that should change set "isSaved"-state of the project
	private void notifyObservers( Action a ){
		App.setNotifying(true);
		setChanged();
		super.notifyObservers(a);
		App.setNotifying(false);
	}
	
	/**
	 * Enumerator that explains the different perspectives.	
	 */
	public static enum Perspective{
		OVERVIEW,PIANOROLL,NOTATION;
	}

	/**
	 * Enumerator that explains the different tools.	
	 */
	public static enum Tool{
		POINTER,PENCIL,ERASER;
	}
	
	/**
	 * Enumerator that explains the different kind of event properties 
	 * that is editable.	
	 */
	public static enum EventProperty{
		TEMPO_CHANGE,VELOCITY;
	}
	
}
